package com.java.basis.io.nio.two;

import org.hibernate.type.CharacterNCharType;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * 目标：服务端群聊系统的实现
 *
 * @author wangning
 * @create 2022-01-11 16:34
 */
public class Server {
	//1.定义一些成员属性，选择器，服务端通道，端口
	private Selector selector;
	private ServerSocketChannel ssChannel;
	private static final int PORT = 9999;

	//2.定义初始化代码逻辑
	public Server() {
		try {
			//a.创建选择器
			selector = Selector.open();
			//b.获取通道
			ssChannel = ServerSocketChannel.open();
			//c.绑定客户端连接的端口
			ssChannel.bind(new InetSocketAddress(PORT));
			//d.把通道模式设置为非阻塞模式
			ssChannel.configureBlocking(false);
			//e.把通道注册到选择器上，并且开始指定连接事件
			ssChannel.register(selector, SelectionKey.OP_ACCEPT);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public Server(Selector selector, ServerSocketChannel ssChannel) {
		this.selector = selector;
		this.ssChannel = ssChannel;
	}

	/**
	 * 开始监听
	 */
	private void listen() {
		try {
			while (selector.select() > 0) {
				//a.获取选择器当中所有注册通道的就绪事件
				Iterator<SelectionKey> it = selector.selectedKeys().iterator();
				//b.开始遍历所有事件
				while (it.hasNext()) {
					//提取事件
					SelectionKey sk = it.next();
					//判断这个事件类型
					if (sk.isAcceptable()) {
						//客户端接入事件，获取客户端通道
						SocketChannel sChannel = ssChannel.accept();
						//注册为非阻塞模式
						sChannel.configureBlocking(false);
						//把通道注册给选择器
						sChannel.register(selector, SelectionKey.OP_READ);

					} else if (sk.isReadable()) {
						//开始处理客户端消息，接收它然后进行转发
						readClientData(sk);

					}
					it.remove();//处理完毕移除当前事件

				}

			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 接收当前客户端通道的消息，转发给全部客户端通道
	 * @param sk
	 */
	private void readClientData(SelectionKey sk) {
		SocketChannel sChannel = null;
		try {
			//直接获取客户端通道
			sChannel = (SocketChannel) sk.channel();
			//创建缓冲区对象开始接收客户端通道的数据
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			int count = sChannel.read(buffer);
			if (count > 0) {
				buffer.flip();
				//提取读到的信息
				String msg = new String(buffer.array(), 0, buffer.remaining());
				System.out.println("接收到了客户端消息：" + msg);
				//把这个消息推送给全部的客户端接收
				sendMsgToAllClient(msg, sChannel);
			}
		} catch (Exception e) {
//			e.printStackTrace();
			//当前客户端已经离线
			sk.cancel();
			try {
				assert sChannel != null;
				System.out.println("有人离线了" + sChannel.getRemoteAddress());
				sChannel.close();
			} catch (IOException ex) {
				ex.printStackTrace();
			}

		}
	}

	/**
	 * 把当前客户端的消息数据推送给当前全部在线注册的channel
	 *
	 * @param msg
	 * @param sChannel
	 */
	private void sendMsgToAllClient(String msg, SocketChannel sChannel) throws IOException {
		System.out.println("服务端开始转发消息：" + Thread.currentThread().getName());
		for (SelectionKey key : selector.keys()) {
			Channel channel = key.channel();
			//不要把数据发给自己
			if (channel instanceof SocketChannel && channel != sChannel) {

				ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
				((SocketChannel) channel).write(buffer);

			}
		}
	}

	public static void main(String[] args) {
		//创建服务端对象
		Server server = new Server();
		//开始监听客户端的各种事件，连接消息，群聊消息，离线消息
		server.listen();

	}
}
